package com.lin.java.tool.redis.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

/**
 * @作者: 林江
 * @创建时间: 2024/3/18
 * @功能:
 */
@Component
public class RedisService {
    @Autowired
    RedisTemplate redis;

    /*-----------------------String-------------------------------*/
    public <K, V> void setStr(K k, V v) {
        redis.opsForValue().set(k, v);
    }

    public <K, V> void setDayStr(K k, V v, long time) {
        this.setExpStr(k, v, time, TimeUnit.DAYS);
    }

    public <K, V> void setExpStr(K k, V v, long time, TimeUnit unit) {
        redis.boundValueOps(k).set(v, time, unit);
    }

    public <K> String getStr(K k) {
        if (exist(k)) {
            return (String) redis.opsForValue().get(k);
        }
        return "";
    }

    /*------------------------------Hash------------------------------------*/
    public <HK, K, V> void setHash(HK hk, K k, V v) {
        this.setExpHash(hk, k, v, -1L, null);
    }

    public <HK, V> void setHash(HK hk, Map<?, V> map) {
        this.setExpHashs(hk, map, -1L, null);
    }

    public <HK, K, V> void setDayHash(HK hk, K k, V v, long time) {
        this.setExpHash(hk, k, v, time, TimeUnit.DAYS);
    }

    public <HK, K, V> void setExpHash(HK hk, K k, V v, long time, TimeUnit unit) {
        redis.boundHashOps(hk).put(k, v);
        this.expire(hk, time, unit);
    }

    public <HK, V> void setExpHashs(HK hk, Map<?, V> map, long time, TimeUnit unit) {
        redis.boundHashOps(hk).putAll(map);
        this.expire(hk, time, unit);
    }

    public <HK, K, T> T getHash(HK hk, K k) {
        if (exist(hk)) {
            return (T) redis.opsForHash().get(hk, k);
        }
        return null;
    }

    public <HK, T> Map<String, T> getHashValues(HK hk, Class<T> c) {
        if (exist(hk)) {
            return (Map<String, T>) redis.opsForHash().values(hk);
        }
        return null;
    }

    public <HK, K> void removeHash(HK hk, K k) {
        if (exist(hk)) {
            redis.boundHashOps(hk).delete(k);
        }
    }

    /*-------------------------------其他----------------------------*/
    public <K> void expire(K k, long time, TimeUnit unit) {
        redis.expire(k, time, unit);
    }

    public <K> boolean exist(K k) {
        return redis.hasKey(k);
    }

    public <K> void delete(K k) {
        redis.delete(k);
    }


    public <K> void refresh(K k) {
        if (exist(k)) {
            this.delete(k);
        }
    }

    /*-----------------------------redis锁---------------------------------*/

    /**
     * 多线程情况下，线程阻塞
     *
     * @return
     */
    public <K, T> T executeLock(Supplier<T> operate, String hk, K k) {
        int count = 0, threshold = 600;
        while (++count < threshold) {
            String lockName = hk.concat("lock");
            Boolean lock = redis.opsForValue().setIfAbsent(lockName, 1, Duration.ofSeconds(30));
            if (lock) {// 上锁
                try {
                    return execute(operate, hk, k);
                } finally {
                    if (Boolean.TRUE.equals(this.exist(lockName))) {
                        this.delete(lockName); // 解锁
                    }
                }
            } else {
                try {
                    TimeUnit.MILLISECONDS.sleep(50);
                } catch (InterruptedException e) {
                }
            }
        }
        throw new RuntimeException("加锁失败！");
    }

    public <K, T> T execute(Supplier<T> operate, String hk, K k) {
        // 从缓存获取数据
        T hashValue = this.getHash(hk, k);
        if (Objects.nonNull(hashValue)) {
            return hashValue;
        }

        // 执行具体的业务
        T data = operate.get();

        // 缓存数据
        if (Objects.nonNull(data)) {
            this.setExpHash(hk, k, data, 1, TimeUnit.DAYS);
        }
        return data;
    }

}
